/*
 * SIT_Dialog.c : manage top level shell windows
 *
 * Written by T.Pierron, Feb 27, 2006.
 */

#include "SIT_P.h"
#include "graphics_P.h"
#include <stdio.h>

	TagList DialogClass[] = {
		{ SIT_SuperClass,  NULL,          ___, SIT_INT, (ULONG) WidgetClass },
		{ SIT_DialogType,  "dialogType",  __G, SIT_INT, OFFSET(SIT_Dialog, sd_Type) },
		{ SIT_DialogState, "dialogState", _SG, SIT_INT, OFFSET(SIT_Dialog, sd_State) },
		{ SIT_Styles,      "styles",      __G, SIT_INT, OFFSET(SIT_Dialog, sd_CustomStyles) },
		{ SIT_Menu,        "menu",        CSG, SIT_PTR, OFFSET(SIT_Dialog, sd_MenuStruct) },
		{ SIT_IconImage,   "iconImage",   CSG, SIT_PTR, OFFSET(SIT_Dialog, sd_Icon) },
		{ SIT_AlphaShape,  "alphaShape",  CSG, SIT_PTR, OFFSET(SIT_Dialog, sd_AlphaShape) },
		{ SIT_TagEnd }
	};


/* Dialog not yet created, need to assign menu bar later */
static int SIT_SetDialogMenu(SIT_Widget w, APTR call_data, APTR user_data)
{
	SetMenu(w->sw_Handle, ((SIT_Dialog)w)->sd_MenuBar);
	w->sw_PostProcess = NULL;
	return 0;
}

static inline HACCEL SIT_CreateAcceleratorTable(SIT_MenuStruct * menu)
{
	SIT_MenuStruct * m;
	ACCEL *          table;
	int              nb;

	for (nb = 0, m = menu; m->level; m ++)
		if (m->accel > 0) nb ++;

	if (nb <= 0) return NULL;

	table = alloca(sizeof *table * nb);

	for (nb = 0, m = menu; m->level; m ++)
	{
		if (m->accel > 0)
		{
			SIT2Accel(m, table + nb);
			nb ++;
		}
	}
	return CreateAcceleratorTable(table, nb);
}

static Image convert_to_img(HIMAGELIST list, int id)
{
	IMAGEINFO info;
	pImageList_GetImageInfo(list, id, &info);
	int width  = info.rcImage.right  - info.rcImage.left + 1;
	int height = info.rcImage.bottom - info.rcImage.top  + 1;
	HDC   memDC = CreateCompatibleDC(NULL);
	Image img   = GFX_CreateImage(width, height, 32);
	HBITMAP old = SelectObject(memDC, img->hbitmap);
	pImageList_Draw(list, id, memDC, 0, 0, ILD_NORMAL);
	/* Sigh. Check that mask can handle 32bpp */
	DATA8 p;
	int i;
	for (p = img->bitmap + 3, width *= height, height = img->bitmap[3], i = width; i > 0 && p[0] == height; i --, p += 4);
	if (i == 0)
	{
		/* Icon from crufty imagelist */
		memset(img->bitmap, 0xff, img->stride * img->height);
		pImageList_Draw(list, id, memDC, 0, 0, ILD_NORMAL);
		for (p = img->bitmap + 3, i = width; i > 0; i --, *p ^= 0xff, p += 4)
			if (*p == 0xff) p[-3] = p[-2] = p[-1] = 0; /* Pre-multiply */
	}
	SelectObject(memDC, old);
	DeleteDC(memDC);
	return img;
}

#define ROUND(nb)    ((nb + 15) & ~15)
#define dialog       ((SIT_Dialog)w)

/* Use 'img' or ('list', 'disa' and 'id') but not all of them */
int SIT_AddMenuIcon(SIT_Widget w, int menuid, HIMAGELIST list, HIMAGELIST disa, int id, Image img)
{
	SIT_MenuIcon mi;
	int cnt;
	/* Check for free space */
	for (cnt = dialog->sd_MICount, mi = dialog->sd_MenuIcons; cnt > 0 && (mi->img || mi->imgList); cnt --, mi ++);
	if (img == NULL)
	{
		MENUITEMINFO inf = {.cbSize = sizeof inf, .fMask = MIIM_DATA | MIIM_FTYPE};
		/* Check if the menu exists or accepts icons */
		if (! GetMenuItemInfo(dialog->sd_MenuBar, menuid, FALSE, &inf) || inf.dwItemData == -1 || inf.dwItemData > 0 ||
		   (inf.fType & MFT_RADIOCHECK))
			return 0;
		inf.fMask = MIIM_DATA | MIIM_BITMAP;
		inf.hbmpItem = HBMMENU_CALLBACK;
		inf.dwItemData = dialog->sd_MICount - cnt + 1;
		if (win32vers > VER_WINXP && has_theme && (img = convert_to_img(list, id)))
			inf.hbmpItem = img->hbitmap;
		SetMenuItemInfo(dialog->sd_MenuBar, menuid, FALSE, &inf);
	}
	if (cnt == 0)
	{
		cnt = dialog->sd_MICount;
		if (ROUND(cnt) <= cnt)
		{
			/* Need to enlarge the buffer */
			mi = realloc(dialog->sd_MenuIcons, ROUND(cnt+1) * sizeof *mi);
			if (mi == NULL) return 0;
			dialog->sd_MenuIcons = mi;
			mi += cnt;
		}
		dialog->sd_MICount ++;
	}
	mi->imgList = list;
	mi->imgDisa = disa;
	mi->imgId   = id;
	mi->img     = img;
	mi->menuId  = menuid;
	if (img) {
		mi->width  = img->width;
		mi->height = img->height;
	} else {
		IMAGEINFO info;
		pImageList_GetImageInfo(list, id, &info);
		mi->width  = info.rcImage.right  - info.rcImage.left + 1;
		mi->height = info.rcImage.bottom - info.rcImage.top  + 1;
		/* The API cannot deal with 32bpp images. Sigh. */
		if (disa == (HIMAGELIST)-1)
			/* Create an image instead and do disable state on the fly */
			mi->img = convert_to_img(list, id);
	}
	return mi - dialog->sd_MenuIcons + 1;
}

void SIT_DelMenuIcon(SIT_Widget w, int id)
{
	if (0 <= id && id < dialog->sd_MICount)
		memset(dialog->sd_MenuIcons + id, 0, sizeof (struct SIT_MenuIcon_t));
}

#if 0 // XXX not used
/* Icon in ImageList has been removed, readjust image Ids */
void SIT_DelMenuIconHList(SIT_Widget w, HIMAGELIST list, int id)
{
	SIT_MenuIcon mi;
	int cnt;
	for (cnt = dialog->sd_MICount, mi = dialog->sd_MenuIcons; cnt > 0; cnt --, mi ++)
	{
		if (mi->imgId == id) memset(mi, 0, sizeof *mi); else
		if (mi->imgId <  id) mi->imgId --;
	}
}
#endif
#undef dialog
#undef ROUND

/* Menu bar need a special processing */
static int SIT_SetDialogValues(SIT_Widget w, APTR call_data, APTR user_data)
{
	TagList *     tag    = call_data;
	SIT_Variant * value  = user_data;
	SIT_Dialog    dialog = (SIT_Dialog) w;

	switch (tag->tl_TagID) {
	case SIT_Menu:
		/* Remove old menu if any */
		if (dialog->sd_MenuBar)
		{
			SetMenu(w->sw_Handle, NULL);
			DestroyMenu(dialog->sd_MenuBar);
		}
		if (dialog->sd_Accel)
			DestroyAcceleratorTable(dialog->sd_Accel);

		dialog->sd_MenuBar = SIT_CreateMenu(w, CreateMenu(), value->pointer, 0);
		dialog->sd_Accel   = SIT_CreateAcceleratorTable(value->pointer);

		if (w->sw_Handle)
		{
			SetMenu(w->sw_Handle, dialog->sd_MenuBar);
			if (w->sw_Visible)
				w->sw_Flags |= SITF_GeometryChanged;
		}
		else w->sw_PostProcess = SIT_SetDialogMenu;
		return 0;
	case SIT_DialogState:
		dialog->sd_State = value->integer;
		switch (dialog->sd_State) {
		case SITV_Flashing:
		{	FLASHWINFO info = {.cbSize = sizeof info, .hwnd = w->sw_Handle, .dwFlags = FLASHW_ALL|FLASHW_TIMERNOFG, .uCount = 4};
			FlashWindowEx(&info);
		}	break;
		case SITV_Normal:    ShowWindow(w->sw_Handle, SW_SHOWNORMAL); break;
		case SITV_Hidden:    ShowWindow(w->sw_Handle, SW_HIDE); break;
		case SITV_Minimized: ShowWindow(w->sw_Handle, SW_SHOWMAXIMIZED); break;
		case SITV_Maximized: ShowWindow(w->sw_Handle, SW_SHOWMINIMIZED); break;
		}
		return 0;
	case SIT_IconImage:
		if (w->sw_Handle)
		{
			HICON icon = SIT_IconFromBitmap(value->pointer);
			if (icon == 0) return 0;
			if (dialog->sd_Icon) DestroyIcon(dialog->sd_Icon);
			dialog->sd_Icon = icon;
			SetClassLongPtr(w->sw_Handle, GCLP_HICON, (ULONG) icon);
		}
		else dialog->sd_Icon = value->pointer;
		return 0;
	default:
		return SIT_SetWidgetValue(w, call_data, user_data); // super class
	}
}

static int SIT_FinalizeDialog(SIT_Widget w, APTR call_data, APTR user_data)
{
	SIT_Dialog dialog = (SIT_Dialog) w;
	SIT_MenuIcon mi;
	int cnt;
	if (dialog->sd_Accel) DestroyAcceleratorTable(dialog->sd_Accel);
	if (dialog->sd_AlphaShape) GFX_FreeImage(dialog->sd_AlphaShape);
	if (dialog->sd_Icon) DestroyIcon(dialog->sd_Icon);
	/* If a window still uses this class, it will not be unregistered */
	UnregisterClass(w->sw_Name, instance);
	/* Icons allocated for menu */
	for (cnt = dialog->sd_MICount, mi = dialog->sd_MenuIcons; cnt > 0; cnt --, mi ++)
	{
		if (mi->img) GFX_FreeImage(mi->img);
	}
	if ((mi = dialog->sd_MenuIcons)) free(mi);
	return 0;
}

BOOL SIT_CreateDialog(SIT_Widget w, va_list args)
{
	SIT_Dialog dialog = (SIT_Dialog) w;
	SIT_Widget parent = NULL;
	int        style  = 0;
	int        ext    = 0;
	int        clsId  = CS_DBLCLKS;
	WCHAR      clsName[48];
	HWND       wnd;

	/* Default class */
	w->sw_SetValue = SIT_SetDialogValues;
	w->sw_Finalize = SIT_FinalizeDialog;
	w->sw_Flags |= SITF_TopLevel;

	SIT_ParseTags(w, args, w->sw_Class = DialogClass);

	LPWSTR title = w->sw_Name;

	if (w->sw_Title)
		UTF8ToUTF16(w->sw_Title, title);

	/* If no icon specified, try to load resource id 1 (usually app's icon) */
	if (dialog->sd_Icon == 0)
		dialog->sd_Icon = (HICON) 1;

	switch (dialog->sd_Type) {
	case SITV_TopLevel: style = WS_OVERLAPPEDWINDOW; break;
	case SITV_ModalEvt: // no break;
	case SITV_Modal:    style = WS_OVERLAPPEDWINDOW & ~WS_MINIMIZEBOX; ext = WS_EX_DLGMODALFRAME; break;
	case SITV_Plain:    style = WS_POPUP; break;
	case SITV_Floating: style = WS_POPUPWINDOW | WS_CAPTION; ext = WS_EX_TOOLWINDOW; break;
	}

//	if (dialog->sd_CustomStyles & SITV_Tranparent) ext |= WS_EX_TRANSPARENT;
	if (dialog->sd_CustomStyles & SITV_DoubleBuffer) ext |= WS_EX_COMPOSITED;
	if (dialog->sd_CustomStyles & SITV_NoResize) style &= ~(WS_MAXIMIZEBOX | WS_THICKFRAME);
	if (dialog->sd_CustomStyles & SITV_NoActivate) ext |= WS_EX_NOACTIVATE;
	if (dialog->sd_CustomStyles & SITV_DropShadow) clsId |= CS_DROPSHADOW;
	if (dialog->sd_CustomStyles & SITV_HasResize) style |= WS_THICKFRAME;

	Image img = dialog->sd_AlphaShape;
	if (img)
	{
		if (img->bpp == 32 && img->hbitmap) {
			ext |= WS_EX_LAYERED;
			w->sw_Fixed.width  = img->width;
			w->sw_Fixed.height = img->height;
			w->sw_Flags |= SITF_FixedWidth|SITF_FixedHeight;
		}
		else dialog->sd_AlphaShape = NULL;
	}

	if (w->sw_Visible) style |= WS_VISIBLE;

	/* Open a default font that children will inherit */
	if (w->sw_Font == NULL)
		w->sw_Font = sithfont ? sithfont : GetStockObject(DEFAULT_GUI_FONT);

	/* Check in the hierarchy for parent dialog */
	for (parent = w->sw_Parent; parent; parent = parent->sw_Parent)
		if (parent->sw_Type == SIT_DIALOG || parent->sw_Type == SIT_APP) break;

	if (parent == NULL) return FALSE;

	SIT_RegisterClass(w, clsName, clsId, IDC_ARROW, dialog->sd_Icon);

	/* Create window */
	dialog->sd_Styles = style;
	dialog->sd_ExStyles = ext;
	w->sw_Handle = wnd = CreateWindowEx(
		ext, clsName, title, style | WS_CLIPCHILDREN,
		0, 0, 10, 10,
		parent ? parent->sw_Handle : NULL,
		NULL, instance, NULL
	);

	/* For AdjustWindowRect */
	if (dialog->sd_Type == SITV_Plain)
		dialog->sd_Styles &= ~WS_THICKFRAME;

	if (wnd == NULL) return FALSE;

	/* Add a resize grip in bottom right corner to avoid adding a status bar */
	if (dialog->sd_CustomStyles & SITV_HasResize)
	{
		RECT rect;
		SIT_Widget grip = SIT_CreateWidget("_Xg", SIT_SCROLLBAR, w,
			SIT_Right,  SITV_AttachForm, NULL, -32000,
			SIT_Bottom, SITV_AttachForm, NULL, -32000,
			NULL
		);
		SetWindowLongW(grip->sw_Handle, GWL_STYLE, WS_CHILD | WS_VISIBLE | SBS_SIZEGRIP |
			SBS_SIZEBOXBOTTOMRIGHTALIGN);
		GetWindowRect(grip->sw_Handle, &rect);
		grip->sw_OptimalBox.cx = rect.right;
		grip->sw_OptimalBox.cy = rect.right;
		// Will tell the layout manager to not take this control into account to
		// check whether or not the dialog can be resized
		grip->sw_Flags |= SITF_Internal;
	}

	if (dialog->sd_Icon)
	{
		dialog->sd_Icon = SIT_IconFromBitmap((Image)dialog->sd_Icon);
		SetClassLongPtr(wnd, GCLP_HICON, (ULONG) dialog->sd_Icon);
	}

	if (w->sw_Font)
		SendMessage(wnd, WM_SETFONT, (WPARAM) w->sw_Font, FALSE);

	if (parent && (dialog->sd_Type == SITV_Modal || dialog->sd_Type == SITV_ModalEvt))
		EnableWindow(dialog->sd_Modal = parent->sw_Handle, FALSE);

	if (w->sw_PostProcess)
		w->sw_PostProcess(w, NULL, NULL);

	/* Keep track of class inside callback */
	SetProp(wnd, sit_class, (HANDLE) w);

	return TRUE;
}

void SIT_UpdateLayeredWindow(SIT_Widget w)
{
	Image img = ((SIT_Dialog)w)->sd_AlphaShape;

	if (img)
	{
		HDC     scr = CreateDC(L"DISPLAY", NULL, NULL, NULL);
		HDC     hdc = CreateCompatibleDC(scr);
		HBITMAP old = SelectObject(hdc, img->hbitmap);
		POINT   pt = {}, orig = {};
		BLENDFUNCTION bf = {
			.BlendOp = AC_SRC_OVER,
			.AlphaFormat = AC_SRC_ALPHA,
			.SourceConstantAlpha = 255
		};
		RECT rect;
		SIZE sz;
		GetClientRect(w->sw_Handle, &rect);
		ClientToScreen(w->sw_Handle, &orig);
		sz.cx = rect.right;
		sz.cy = rect.bottom;
		if (HAS_EVT(w, SITE_OnPaint))
		{
			GCBuf gc;
			RECT  rc = {.right = img->width, .bottom = img->height};
			GFX_InitGC(&gc, w->sw_Handle, hdc, &rc, w->sw_Font, w->sw_BgBrush);
			SIT_ApplyCallback(w, &gc, SITE_OnPaint);
			GFX_Free(&gc);
		}
		UpdateLayeredWindow(w->sw_Handle, scr, &orig, &sz, hdc, &pt, 0, &bf, ULW_ALPHA);
		SelectObject(hdc, old);
		DeleteDC(hdc);
		DeleteDC(scr);
	}
}

/*
 * The only advantage in using this function instead of SIT_RemoveWidget() is that you
 * can use it on any non top-level child of the dialog to close it.
 */
DLLIMP void SIT_CloseDialog(SIT_Widget w)
{
	while (w && w->sw_Type != SIT_DIALOG)
		w = w->sw_Parent;

	if (w == NULL) return;

	if (HAS_EVT(w, SITE_OnClose))
	{
		Bool param = True;
		SIT_ApplyCallback(w, &param, SITE_OnClose);
		if (! param) return;
	}
	SIT_RemoveWidget(w);
}
